#ifndef BLOCK_QUEUE_H
#define BLOCK_QUEUE_H

#include <cstdlib>
#include <sys/time.h>
#include "../lock/locker.h"


template<typename T>
class BlockQueue{
public:
    explicit BlockQueue(int capacity){
        if (capacity <= 0) {
            exit(1);
        }

        m_array = new T[capacity];
        m_capacity = capacity;
        m_size = 0;
        m_front = -1;
        m_back = -1;
    }

    ~BlockQueue(){
        m_lock.lock();
        delete [] m_array;
        m_lock.unlock();
    }

    bool full() {
        m_lock.lock();
        if (m_size >= m_capacity){
            m_lock.unlock();
            return true;
        }
        m_lock.unlock();
        return false;
    }

    bool empty(){
        m_lock.lock();
        if (m_size == 0){
            m_lock.unlock();
            return true;
        }
        m_lock.unlock();
        return false;
    }

    T front(){
        m_lock.lock();
        if (m_size == 0){
            m_lock.unlock();
            return {};
        }
        T elem = m_array[m_front];
        m_lock.unlock();
        return elem;
    }

    T back(){
        m_lock.lock();
        if (m_size == 0){
            m_lock.unlock();
            return {};
        }
        T elem = m_array[m_back];
        m_lock.unlock();
        return elem;
    }

    int size(){
        m_lock.lock();
        int size_val = m_size;
        m_lock.unlock();
        return size_val;
    }

    int capacity(){
        m_lock.lock();
        int cap_val = m_capacity;
        m_lock.unlock();
        return cap_val;
    }

    bool push(const T &item) {
        m_lock.lock();
        if (m_size >= m_capacity) {
            m_cond.broadcast();
            m_lock.unlock();
            return false;
        }
        m_back = (m_back + 1) % m_capacity;
        m_array[m_back] = item;
        if (m_front == -1)
            m_front = 0;
        m_size += 1;

        m_cond.broadcast();
        m_lock.unlock();
        return true;
    }

    bool pop(T &item) {
        m_lock.lock();
        while (m_size <= 0) {
            if (!m_cond.wait(m_lock.get())){
                m_lock.unlock();
                return false;
            }
        }
        item = m_array[m_front];
        m_front = (m_front + 1) % m_capacity;
        m_size -= 1;

        m_lock.unlock();
        return true;
    }

    bool try_to_pop(T &item, unsigned int timeout_ms=10) {
        struct timespec t{};
        m_lock.lock();
        if (m_size <= 0) {
            clock_gettime(CLOCK_MONOTONIC, &t);
            t.tv_sec += timeout_ms / 1000;
            t.tv_nsec = (timeout_ms % 1000) * 1000000;
            if (!m_cond.timedwait(m_lock.get(), t)){
                m_lock.unlock();
                return false;
            }
        }

        if (m_size <= 0) {
            m_lock.unlock();
            return false;
        }

        item = m_array[m_front];
        m_front = (m_front + 1) % m_capacity;
        m_size -= 1;

        m_lock.unlock();
        return true;
    }


private:
    T *m_array;
    int m_size;
    int m_capacity;
    int m_front;
    int m_back;

    Condition m_cond;
    Locker m_lock;
};


#endif //BLOCK_QUEUE_H
